﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;

namespace DAWSharp.UI.WinFormsControls
{
    /// <summary>
    /// Represents a Logarithmic or Linear Fader control
    /// </summary>
    public partial class Fader : UserControl
    {
        /// <summary>
        /// The current value of the fader in Percent (0% - 100%)
        /// </summary>
        public int ValuePercent { get; private set; }
        /// <summary>
        /// The current value of the fader as a float (0.0 - 1.0)
        /// </summary>
        public float ValueFloat { get; private set; }
        /// <summary>
        /// The current value of the fader in decibels (-∞ dB - 0.0 dB)
        /// </summary>
        public float ValueDB { get; private set; }

        private bool _isLog = true;
        private const int maxYPos = 201;
        private const int minYPos = 1;
        private Point faderLocation = new Point(3, 101);
        private bool dragging = false;
        private int dragY;
        private int mouseDownY;

        #region Constructors

        /// <summary>
        /// Creates an instance of a Fader object
        /// </summary>
        public Fader()
        {
            this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
            InitializeComponent();
            SetupFader();
        }

        /// <summary>
        /// Creates an instance of a Fader object in either Logarithmic or Linear mode
        /// </summary>
        /// <param name="Logarithmic">Logarithmic mode true/false</param>
        public Fader(bool logarithmic)
        {
            _isLog = logarithmic;
            this.SetStyle(ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
            InitializeComponent();
            SetupFader();
        }

        #endregion

        #region public methods

        /// <summary>
        /// Occurs when the Fader is Moved
        /// </summary>
        public event EventHandler FaderMoved
        {
            add { imgHandle.LocationChanged += value; }
            remove { imgHandle.LocationChanged -= value; }
        }
        
        /// <summary>
        /// Returns a System.String object containg the name of the fader object and its current values in Percent, Float and dBs.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format(@"{3} values: {0}% | {1}f | {2}dB", ValuePercent.ToString(), ValueFloat.ToString(), ValueDB.ToString(), this.Name);
        }

        #endregion

        #region private methods

        protected override void OnPaint(PaintEventArgs e)
        {
            imgHandle.Location = faderLocation;
            base.OnPaint(e);
        }
        
        private void SetupFader()
        {
            labelValue.Visible = true;
            textBoxValue.Visible = false;
            
            if (!_isLog)
            {             
                SetFaderLocation(50);

                label0dB.Text = "100%";
                label0dB.DoubleClick += (s, e) => SetFaderLocation(100);
                label6dB.Text = "   90%";
                label6dB.DoubleClick += (s, e) => SetFaderLocation(90);
                label12dB.Text = "     80%";
                label12dB.DoubleClick += (s, e) => SetFaderLocation(80);
                label18dB.Text = "     70%";
                label18dB.DoubleClick += (s, e) => SetFaderLocation(70);
                label24dB.Text = "     60%";
                label24dB.DoubleClick += (s, e) => SetFaderLocation(60);
                label30dB.Text = "     50%";
                label30dB.DoubleClick += (s, e) => SetFaderLocation(50);
                label36dB.Text = "     40%";
                label36dB.DoubleClick += (s, e) => SetFaderLocation(40);
                label42dB.Text = "     30%";
                label42dB.DoubleClick += (s, e) => SetFaderLocation(30);
                label48dB.Text = "     20%";
                label48dB.DoubleClick += (s, e) => SetFaderLocation(20);
                label54dB.Text = "     10%";
                label54dB.DoubleClick += (s, e) => SetFaderLocation(10);
                labelInfdB.Text = "        0%";
                labelInfdB.DoubleClick += (s, e) => SetFaderLocation(0);
            }
            else
            {
                SetFaderLocation(-30);

                label0dB.Text = "0.0";
                label0dB.DoubleClick += (s, e) => SetFaderLocation(0);
                label6dB.Text = "-6.0";
                label6dB.DoubleClick += (s, e) => SetFaderLocation(-6);
                label12dB.Text = "-12.0";
                label12dB.DoubleClick += (s, e) => SetFaderLocation(-12);
                label18dB.Text = "-18.0";
                label18dB.DoubleClick += (s, e) => SetFaderLocation(-18);
                label24dB.Text = "-24.0";
                label24dB.DoubleClick += (s, e) => SetFaderLocation(-24);
                label30dB.Text = "-30.0";
                label30dB.DoubleClick += (s, e) => SetFaderLocation(-30);
                label36dB.Text = "-36.0";
                label36dB.DoubleClick += (s, e) => SetFaderLocation(-36);
                label42dB.Text = "-42.0";
                label42dB.DoubleClick += (s, e) => SetFaderLocation(-42);
                label48dB.Text = "-48.0";
                label48dB.DoubleClick += (s, e) => SetFaderLocation(-48);
                label54dB.Text = "-54.0";
                label54dB.DoubleClick += (s, e) => SetFaderLocation(-54);
                labelInfdB.Text = "-∞ dB";
                labelInfdB.DoubleClick += (s, e) => SetFaderLocation(-100);
            }
        }

        private void SetValues(bool RoundLabels)
        {
            if (!_isLog)
            {
                //Normalise the scale so it is in range 0-100
                ValuePercent = (201 - faderLocation.Y) / 2;
                ValueFloat = ValuePercent / 100.0f;

                /* Calculate DBs from this value using dB = 20Log10(V1/V2)
                 * Where V1 is current value and V2 is max value - in this case 1 */
                ValueDB = 20.0f * (float)Math.Log10(ValueFloat / 1.0f);
                
                textBoxValue.Text = string.Format("{0}", ValuePercent);
                labelValue.Text = string.Format("{0} %", ValuePercent);
            }
            else
            {
                /*Calculate the faders position on a log scale (powers of 2)
                 * First the scale is normalised so it is in the range 0-10, then used as a power of 2 */           
                float logNormalizedValue = (float)Math.Pow(2, ((201 - faderLocation.Y)) / 20.0f);
                
                /* Calculate DBs from this value using dB = 20Log10(V1/V2)
                 * Where V1 is current value and V2 is max value - in this case 2^10 or 1024*/
                ValueDB = 20.0f * (float)Math.Log10(logNormalizedValue / 1024);
                
                /* Calucate the value as a float by rearranging the previous equation */
                ValueFloat = (float)(Math.Pow(10, (ValueDB / 20)));
                ValuePercent = (int)(100 * ValueFloat);

                //Because the fader only has a resolution of 200px we cannot accurately jump to an exact decibel value (e.g -6.00).
                //When -6 is entered, or -6 label is clicked, the position is actually apprx -5.9. 
                //The below logic allows us to round up on the UI if the user specifies an exact value.
                if (!RoundLabels)
                {
                    textBoxValue.Text = string.Format("{0:f2}", ValueDB);
                    labelValue.Text = string.Format("{0:f2} dB", ValueDB);
                }
                else
                {
                    textBoxValue.Text = string.Format("{0:f2}", Math.Round(ValueDB));
                    labelValue.Text = string.Format("{0:f2} dB", Math.Round(ValueDB));
                }
            }
        }

        private void SetFaderLocation(float value)
        {
            if (!_isLog)
            {
                //rearranged form of ValuePercent = (201 - faderLocation.Y) / 2 - See SetValues()
                int Y = -(((int)value * 2) - 201);
                faderLocation = new Point(3, Y);

                if (faderLocation.Y < minYPos)
                    faderLocation = new Point(3, 1);
                else if (faderLocation.Y > maxYPos)
                    faderLocation = new Point(3, 201);

                imgHandle.Location = faderLocation;
                SetValues(false);
            }
            else
            {
                //Rearranged form of ValueDB = 20.0f * Math.Log10(logNormalizedValue / 1024) - See SetValues()
                float logNormalizedValue = (float)(Math.Pow(10, (value / 20)) * 1024);
                //Rearranged form of logNormalizedValue = Math.Pow(2, ((201 - faderLocation.Y)) / 20.0f) - See SetValues()
                int Y = (int)-(20 * (Math.Log10(logNormalizedValue) / Math.Log10(2)) - 201);
                faderLocation = new Point(3, Y);

                if (faderLocation.Y < minYPos)
                    faderLocation = new Point(3, 1);
                else if (faderLocation.Y > maxYPos)
                    faderLocation = new Point(3, 201);

                imgHandle.Location = faderLocation;
                SetValues(true);
            }
        }

        #endregion

        #region event handlers

        private void imgHandle_MouseDown(object sender, MouseEventArgs e)
        {
            dragging = true;
            dragY = e.Y - imgHandle.Location.Y;
            mouseDownY = e.Y;
            base.OnMouseDown(e);
        }

        private void imgHandle_MouseUp(object sender, MouseEventArgs e)
        {
            dragging = false;
            SetValues(false);
            base.OnMouseUp(e);
        }

        private void imgHandle_MouseMove(object sender, MouseEventArgs e)
        {
            if (dragging)
            {
                //Detect the direction the mouse is moving in follow it at the same velocity
                if (e.Y > mouseDownY)
                    faderLocation = new Point(3, faderLocation.Y + (1 * e.Y - mouseDownY));
                else if (e.Y < mouseDownY)
                    faderLocation = new Point(3, faderLocation.Y - (1 * mouseDownY - e.Y));

                if (faderLocation.Y < minYPos)
                    faderLocation = new Point(3, 1);
                else if (faderLocation.Y > maxYPos)
                    faderLocation = new Point(3, 201);

                SetValues(false);

                this.Refresh();
            }

            base.OnMouseMove(e);
        }

        private void imgSlot_MouseClick(object sender, MouseEventArgs e)
        {
            faderLocation = new Point(3, e.Y - 16);

            if (faderLocation.Y < minYPos)
                faderLocation = new Point(3, 1);
            else if (faderLocation.Y > maxYPos)
                faderLocation = new Point(3, 201);

            SetValues(false);
            this.Refresh();
        }

        private void labelValue_DoubleClick(object sender, EventArgs e)
        {
            labelValue.Visible = false;
            textBoxValue.Visible = true;
            textBoxValue.Focus();
            textBoxValue.SelectAll();
        }

        private void textBoxValue_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Return)
            {
                labelValue.Visible = true;
                textBoxValue.Visible = false;

                float result;
                //Regex only allows positive or negative floating or fixed point numbers. The TryParse is possibly uneccessary.
                if (float.TryParse(Regex.Replace(textBoxValue.Text, "[^-0-9.]", ""), out result))
                {
                    SetFaderLocation(result);
                }
            }
        }

        #endregion
    }
}
